package framework;



import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.w3c.dom.Element;

import cocola.AbstractType;
import cocola.Class;
import cocola.Cocola;
import cocola.CocolaFactory;
import cocola.CocolaPackage;
import cocola.Component;
import cocola.DirectedInterface;
import cocola.Interface;
import cocola.InterfaceRole;
import cocola.Operation;
import cocola.Parameter;
import cocola.ParameterDirection;
import cocola.Primitive;
import cocola.impl.DirectedInterfaceImpl;
import framework.entity.ACLFunction;
import framework.entity.ACLInterface;

public class HierarchyCocola {

	private String filePath;

	private Element node_root;

	private Cocola model;

	
	// hierarchie des types parametres interfaces composant
	private HashMap<String, ArrayList<String>> hierarchieTypeDown;
	private HashMap<String, ArrayList<String>> hierarchieTypeUp;
	
	// fonctions requise et fournie
	private HashMap<String, ArrayList<ACLFunction>> fonctionsProvided;
	private HashMap<String, ArrayList<ACLFunction>> fonctionsRequired;
	
	private HashMap<String, ArrayList<ACLFunction>> fonctions;
	
	// hash map simpifiant les commposantes connexes entre fonction et interface
	private HashMap<String, ArrayList<String>> functiontoRequiredInterface;
	private HashMap<String, ArrayList<String>> functiontoProvidedInterface;
	
	private HashMap<String, ArrayList<String>> functionsToInterfaces;
	private HashMap<String, ArrayList<String>> interfacesToFunctions;
	
	// une liste de fonction pour chaque interfaces
	private HashMap<String, ArrayList<ACLFunction>> interfaces;
	private HashMap<String, ArrayList<String>> interfacesRequired;
	private HashMap<String, ArrayList<String>> interfacesProvided;
	
	
	private HashMap<String, ArrayList<ACLInterface>> componentsProvided;
	private HashMap<String, ArrayList<ACLInterface>> componentsRequired;

	private HashMap<String, ArrayList<String>> componentstoFunctions;
	private HashMap<String, ArrayList<String>> functionstoComponents;
	
		private HashMap<String, ArrayList<String>> interfacesToComponent;
	private HashMap<String, ArrayList<String>> componentsTointerface;

	public HierarchyCocola(String url) {
		initialiseStructure();

		ResourceSet resourceSet = getCocolaResourceSet();
		URI uri = URI.createFileURI(new File(url).getAbsolutePath());
		Resource resource = resourceSet.getResource(uri, true);
		model = ((Cocola) resource.getContents().get(0));

		createSuperSousType();
		calculedHierarchy();
		CalculedComponent();
		
	}

	public void initialiseStructure() {
		hierarchieTypeDown = new HashMap<String, ArrayList<String>>();
		hierarchieTypeUp = new HashMap<String, ArrayList<String>>();
		fonctionsProvided = new HashMap<String, ArrayList<ACLFunction>>();
		fonctionsRequired = new HashMap<String, ArrayList<ACLFunction>>();
		interfaces = new HashMap<String, ArrayList<ACLFunction>>();

		interfacesProvided = new HashMap<String, ArrayList<String>>();
		interfacesRequired = new HashMap<String, ArrayList<String>>();

		functiontoProvidedInterface = new HashMap<String, ArrayList<String>>();
		functiontoRequiredInterface = new HashMap<String, ArrayList<String>>();
		
		functionsToInterfaces = new HashMap<String, ArrayList<String>>();
		interfacesToFunctions = new HashMap<String, ArrayList<String>>();
		
		fonctions = new HashMap<String, ArrayList<ACLFunction>>();

		componentsProvided = new HashMap<String, ArrayList<ACLInterface>>();
		componentsRequired = new HashMap<String, ArrayList<ACLInterface>>();
		componentsTointerface = new HashMap<String, ArrayList<String>>();

		componentstoFunctions = new HashMap<String, ArrayList<String>>();
		functionstoComponents = new HashMap<String, ArrayList<String>>();

		interfacesToComponent = new HashMap<String, ArrayList<String>>();
	}

	public String getfilePath() {return filePath;}
	public Element getElement() {return node_root;}

	public Cocola getCocola() {return model;}
	public HashMap<String, ArrayList<String>> getHierarchyUp() {return hierarchieTypeUp;}
	public HashMap<String, ArrayList<String>> getHierarchyDown() {return hierarchieTypeDown;}
	public HashMap<String, ArrayList<ACLFunction>> getFonctionsProvided() {return fonctionsProvided;}
	public HashMap<String, ArrayList<ACLFunction>> getFonctionsRequired() {return fonctionsRequired;}
	public HashMap<String , ArrayList<ACLFunction>> getFonctions(){return fonctions;}
	public HashMap<String, ArrayList<ACLFunction>> getInterfacesFonction() {return interfaces;}
	public HashMap<String, ArrayList<String>> getInterfacesProvided() {return interfacesProvided;}
	public HashMap<String, ArrayList<String>> getInterfacesRequired() {return interfacesRequired;}
	public HashMap<String, ArrayList<ACLInterface>> getComponentsRequired() {return componentsRequired;}
	public HashMap<String, ArrayList<String>> getComponentsToInterface() {return componentsTointerface;}
	public HashMap<String, ArrayList<ACLInterface>> getComponentsProvided() {return componentsProvided;}
	public HashMap<String, ArrayList<String>> getComponentstoFunctions() {return componentstoFunctions;}
	public HashMap<String, ArrayList<String>> getFunctionstoComponents() {return functionstoComponents;}
	public HashMap<String, ArrayList<String>> getFonctionsRequiredInterfaces() {return functiontoRequiredInterface;}
	public HashMap<String, ArrayList<String>> getFonctionsProvidedInterfaces() {return functiontoProvidedInterface;}
	public HashMap<String, ArrayList<String>> getInterfacesToComponent() {return interfacesToComponent;}

	
	public HashMap<String, ArrayList<String>> getFunctionsToInterfaces(){return functionsToInterfaces;}
	public HashMap<String, ArrayList<String>> getInterfacesToFunctions(){return interfacesToFunctions;}
	
	
	private static ResourceSet getCocolaResourceSet() {
		ResourceSet resourceSet = new ResourceSetImpl();
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap()
		.put(Resource.Factory.Registry.DEFAULT_EXTENSION,
				new XMIResourceFactoryImpl());
		resourceSet.getPackageRegistry().put(CocolaPackage.eNS_URI,
				CocolaPackage.eINSTANCE);
		return resourceSet;
	}

	// Va ajoute un nouveau type ANY qui sera un super sous type
	public void createSuperSousType() {

		Class c = CocolaFactory.eINSTANCE.createClass();
		c.setName("ANY");
		for (Class cl : (EList<Class>) model.getClasses()) {
			c.getSuperTypes().add(cl);
		}
		model.getClasses().add(c);

	}

	// ================================================================================
	// === Calcul de la hierarchy totale de tous les types
	// ================================================================================

	public void lookSuperClass() {
		for (Class cl : (EList<Class>) model.getClasses()) {
			ArrayList<String> supertype = new ArrayList<String>();
			for (AbstractType type : (EList<AbstractType>) cl.getSuperTypes()) {
				supertype.add(type.getName());
				//System.out.println("super class " + type.getName());
				if (!hierarchieTypeDown.containsKey(type.getName()))
					hierarchieTypeDown.put(type.getName(),
							new ArrayList<String>());

				if (!hierarchieTypeDown.get(type.getName()).contains(
						cl.getName()))
					hierarchieTypeDown.get(type.getName()).add(cl.getName());
			}
			hierarchieTypeUp.put(cl.getName(), supertype);
			if (!hierarchieTypeDown.containsKey(cl.getName()))
				hierarchieTypeDown.put(cl.getName(), new ArrayList<String>());
		}

	}

	public void lookSuperInterface() {
		for (Interface inter :(EList<Interface>) model.getInterfaces()){
			ArrayList<String> supertype = new ArrayList<String>();
			for (AbstractType type :(EList<AbstractType>) inter.getSuperTypes()){
				supertype.add(type.getName());
				if (!hierarchieTypeDown.containsKey(type.getName()))
					hierarchieTypeDown.put(type.getName(),new ArrayList<String>());

				if (!hierarchieTypeDown.get(type.getName()).contains(inter.getName()))
					hierarchieTypeDown.get(type.getName()).add(inter.getName());
			}
			hierarchieTypeUp.put(inter.getName(),supertype);
			if (!hierarchieTypeDown.containsKey(inter.getName()))
				hierarchieTypeDown.put(inter.getName(),new ArrayList<String>());
		}


	}

	public void lookSuperComponent() {
		for (Component comp : (EList<Component>) model.getComponents()) {
			ArrayList<String> supertype = new ArrayList<String>();
			for (AbstractType type : (EList<AbstractType>) comp.getSuperTypes()) {
				//System.out.println("super comp " + type.getName());
				supertype.add(type.getName());
				if (!hierarchieTypeDown.containsKey(type.getName()))
					hierarchieTypeDown.put(type.getName(),
							new ArrayList<String>());

				if (!hierarchieTypeDown.get(type.getName()).contains(
						comp.getName()))
					hierarchieTypeDown.get(type.getName()).add(comp.getName());
			}
			hierarchieTypeUp.put(comp.getName(), supertype);
			if (!hierarchieTypeDown.containsKey(comp.getName()))
				hierarchieTypeDown.put(comp.getName(), new ArrayList<String>());
		}

	}

	public void lookSuperPrimitive() {

		for (Primitive prim : (EList<Primitive>) model.getPrimitives()) {
			hierarchieTypeUp.put(prim.getName(), new ArrayList<String>());
			if (!hierarchieTypeDown.containsKey(prim.getName()))
				hierarchieTypeDown.put(prim.getName(), new ArrayList<String>());
		}
	}


	//la on attes la hierarchie
	public void calculedHierarchy() {
		lookSuperClass();
		lookSuperInterface();
		lookSuperComponent();
		lookSuperPrimitive();
		completeDependencyHierarchyType();
	}

	public void completeDependencyHierarchyType() {
		Iterator<String> itdown = hierarchieTypeUp.keySet().iterator();
		while (itdown.hasNext()) {
			String element = itdown.next();
			for (int i = 0; i < hierarchieTypeDown.get(element).size(); i++) {
				dependencyDown(element, hierarchieTypeDown.get(element).get(i));
			}
			for (int i = 0; i < hierarchieTypeUp.get(element).size(); i++) {
				dependencyUp(element, hierarchieTypeUp.get(element).get(i));
			}
		}
	}

	public void dependencyDown(String element, String el) {
		ArrayList<String> array = hierarchieTypeDown.get(element);
		ArrayList<String> array2 = hierarchieTypeDown.get(el);
		if ((!array.contains(el)) && (el != element)) {
			array.add(el);
		}
		if (!array2.isEmpty()) {
			for (int i = 0; i < array2.size(); i++) {
				dependencyDown(element, array2.get(i));

			}
		}
	}

	public void dependencyUp(String element, String el) {
		ArrayList<String> array = hierarchieTypeUp.get(element);
		ArrayList<String> array2 = hierarchieTypeUp.get(el);
		if ((!array.contains(el)) && (el != element)) {
			array.add(el);
		}
		if (!array2.isEmpty()) {
			for (int i = 0; i < array2.size(); i++) {
				dependencyUp(element, array2.get(i));

			}
		}
	}

	// --------------------------------------------------------------------------------
	// --------------------------------------------------------------------------------


	//nouvelle version 
	public void calculedInterface(AbstractType a,Interface inter, int role, Component comp) 
	{
		Interface i = (Interface) a;
		fillStructuresInterfaces(a, role);

		if (!i.getSuperTypes().isEmpty())
		{
			for(int x=0; x<i.getSuperTypes().size(); x++)
			{      	
				Interface ix = (Interface) i.getSuperTypes().get(x);
				calculedInterface(ix,inter,role,comp);
			}
		}
		for (Operation op : (EList<Operation>) i.getOperations()){
			// ATTENTION GERER LES EXCEPTION (A FAIRE)
			ACLFunction strop = new ACLFunction(op.getName());
			for (Parameter para : (EList<Parameter>) op.getParameters()){
				ParameterDirection direction = para.getDirection();
				
				if (direction.getValue() == ParameterDirection.IN_VALUE)
					strop.getIn().add(para.getType().getName());
				else if (direction.getValue() == ParameterDirection.OUT_VALUE)
					strop.addOut(para.getType().getName());
				else if (direction.getValue() == ParameterDirection.INOUT_VALUE){
					strop.getIn().add(para.getType().getName());
					strop.addOut(para.getType().getName());
				}
			}
			fillStropsInterfaces(inter, role, comp ,strop);
			fillStropsInterfaces(i, role, comp,strop);


		}

	}



	public void fillStructuresInterfaces(AbstractType a, int role)
	{
		Interface i = (Interface) a;
		if (!interfaces.containsKey(i.getName())) {
			interfaces.put(i.getName(), new ArrayList<ACLFunction>());
		}      	
		if (!interfacesToFunctions.containsKey(i.getName())) interfacesToFunctions.put(i.getName(),new ArrayList<String>());
	
		
		if (role == InterfaceRole.PROVIDED_VALUE){
			if (!interfacesToComponent.containsKey("PRO-"+i.getName())) interfacesToComponent.put("PRO-"+i.getName(),new ArrayList<String>());
			if (!interfacesProvided.containsKey(i.getName())) interfacesProvided.put(i.getName(),new ArrayList<String>());
		}
		else{
			if (!interfacesToComponent.containsKey("REQ-"+i.getName()))interfacesToComponent.put("REQ-"+i.getName(),new ArrayList<String>());
			if (!interfacesRequired.containsKey(i.getName()))interfacesRequired.put(i.getName(),new ArrayList<String>());
		}
	}



	public void fillStropsInterfaces(Interface inter, int role, Component comp,ACLFunction strop)
	{

		if (!interfaces.get(inter.getName()).contains(strop))interfaces.get(inter.getName()).add(strop);

		if (!fonctions.containsKey(strop.getName())) fonctions.put(strop.getName(),new ArrayList<ACLFunction>());
		if (!fonctions.get(strop.getName()).contains(strop)) fonctions.get(strop.getName()).add(strop);
		
		if (!functionsToInterfaces.containsKey(strop.getName()))functionsToInterfaces.put(strop.getName(),new ArrayList<String>());
		if (!functionsToInterfaces.get(strop.getName()).contains(inter.getName()))functionsToInterfaces.get(strop.getName()).add(inter.getName());
			
		if (!interfacesToFunctions.get(inter.getName()).contains(strop.getName()))interfacesToFunctions.get(inter.getName()).add(strop.getName());
		
		
		if (role == InterfaceRole.PROVIDED_VALUE){
			if (!componentstoFunctions.containsKey(comp.getName()))componentstoFunctions.put(comp.getName(),new ArrayList<String>());
			if (!componentstoFunctions.get(comp.getName()).contains(strop.getName()+"PRO"))componentstoFunctions.get(comp.getName()).add(strop.getName()+"PRO");
			if (!functionstoComponents.containsKey(strop.getName()+"PRO"))functionstoComponents.put(strop.getName()+"PRO",new ArrayList<String>());
			if (!functionstoComponents.get(strop.getName()+"PRO").contains(comp.getName()))functionstoComponents.get(strop.getName()+"PRO").add(comp.getName());
			if (!fonctionsProvided.containsKey(strop.getName()))fonctionsProvided.put(strop.getName(),new ArrayList<ACLFunction>());
			if (!fonctionsProvided.get(strop.getName()).contains(strop))fonctionsProvided.get(strop.getName()).add(strop);
			if (!interfacesProvided.get(inter.getName()).contains(strop.getName()))interfacesProvided.get(inter.getName()).add(strop.getName());
			if (!functiontoProvidedInterface.containsKey(strop.getName()))functiontoProvidedInterface.put(strop.getName(),new ArrayList<String>());
			if (!functiontoProvidedInterface.get(strop.getName()).contains(inter.getName()))functiontoProvidedInterface.get(strop.getName()).add(inter.getName());
			if (!interfacesToComponent.get("PRO-"+inter.getName()).contains(comp.getName()))interfacesToComponent.get("PRO-"+inter.getName()).add(comp.getName());
		}
		else
		{
			if (!fonctionsRequired.containsKey(strop.getName()))fonctionsRequired.put(strop.getName(),new ArrayList<ACLFunction>());
			if (!fonctionsRequired.get(strop.getName()).contains(strop))fonctionsRequired.get(strop.getName()).add(strop);
			if (!functiontoRequiredInterface.containsKey(strop.getName()))functiontoRequiredInterface.put(strop.getName(),new ArrayList<String>());
			if (!functiontoRequiredInterface.get(strop.getName()).contains(inter.getName()))functiontoRequiredInterface.get(strop.getName()).add(inter.getName());
			if (!interfacesRequired.get(inter.getName()).contains(strop.getName()))interfacesRequired.get(inter.getName()).add(strop.getName());
			if (!interfacesToComponent.get("REQ-"+inter.getName()).contains(comp.getName()))interfacesToComponent.get("REQ-"+inter.getName()).add(comp.getName());
			if (!componentstoFunctions.containsKey(comp.getName()))componentstoFunctions.put(comp.getName(),new ArrayList<String>());
			if (!componentstoFunctions.get(comp.getName()).contains(strop.getName()+"REQ"))componentstoFunctions.get(comp.getName()).add(strop.getName()+"REQ");
			if (!functionstoComponents.containsKey(strop.getName()+"REQ"))functionstoComponents.put(strop.getName()+"REQ",new ArrayList<String>());
			if (!functionstoComponents.get(strop.getName()+"REQ").contains(comp.getName()))functionstoComponents.get(strop.getName()+"REQ").add(comp.getName());
		}





	}



	public void CalculedComponent()
	{
		for (Component comp : (EList<Component>) model.getComponents()) 
		{calculedComponent2(comp,comp);	}
	}

	public void printHierarchy()
	{
		System.out.println("		Affichage Hierarchie		  ");
		System.out.println();
		for (Component comp : (EList<Component>) model.getComponents())
		{
			System.out.println("----------------------------------------------");
			System.out.println("Composant : "+comp.getName());
			System.out.println();
			for(String sip : componentsTointerface.get(comp.getName()))
			{
				System.out.println("   interface : "+sip.substring(4));	    
				for(ACLFunction sop : interfaces.get(sip.substring(4)))
				{System.out.println("      operations : "+sop.toString());}
				System.out.println();
			}
			System.out.println("----------------------------------------------");
		}
		System.out.println("           FIN Affichage Hierarchie           ");


	}

	public void calculedComponent2(AbstractType at,Component comp) {	
		if (!componentsProvided.containsKey(comp.getName()))componentsProvided.put(comp.getName(), new ArrayList<ACLInterface>());
		if (!componentsRequired.containsKey(comp.getName()))componentsRequired.put(comp.getName(), new ArrayList<ACLInterface>());
		if (!componentsTointerface.containsKey(comp.getName()))componentsTointerface.put(comp.getName(),new ArrayList<String>());
		componentstoFunctions.put(comp.getName(), new ArrayList<String>());	
		Component a=(Component)at;
		for (DirectedInterface directI : (EList<DirectedInterface>) a.getInterfaces()) 
		{
			Interface inter = directI.getInterface();
			InterfaceRole roleI = directI.getRole();
			calculedInterface(inter ,inter, roleI.getValue(), comp);
			
			ACLInterface aclinter = new ACLInterface(inter.getName());
			aclinter.setFunctions(interfaces.get(inter.getName()));
			
			if (roleI.getValue() == InterfaceRole.PROVIDED_VALUE) {
				if (!componentsProvided.get(comp.getName()).contains(inter.getName()))
					componentsProvided.get(comp.getName()).add(aclinter);
				if (!componentsTointerface.get(comp.getName()).contains("PRO-" + inter.getName()))
					componentsTointerface.get(comp.getName()).add("PRO-" + inter.getName());
			}
			else 
			{
				if (!componentsRequired.get(comp.getName()).contains(inter.getName()))
					componentsRequired.get(comp.getName()).add(aclinter);

				if (!componentsTointerface.get(comp.getName()).contains("REQ-" + inter.getName()))
					componentsTointerface.get(comp.getName()).add("REQ-" + inter.getName());
			}
		}
		if (!a.getSuperTypes().isEmpty())
		{
			for(int x=0; x<a.getSuperTypes().size(); x++)
			{
				try
				{	      	
					Component cx = (Component) a.getSuperTypes().get(x);
					calculedComponent2(cx,comp);
				}
				catch (Exception e)
				{
					//   System.out.println(e.toString());
					//  System.out.println(a.getSuperTypes().get(x).toString());
				}		    
			}
		}
	}


	
	

	public String getMaxSuperType(String s) {

		if (s.equals("ANY"))
			return "void";
		if (hierarchieTypeUp.get(s).size() > 1)
			return hierarchieTypeUp.get(s).get(
					hierarchieTypeUp.get(s).size() - 2);
		else
			return s;
	}

	
	public Boolean isParent(String s1, String s2) {
		
		return hierarchieTypeUp.get(s2).contains(s1);
	}

	public Boolean isChildren(String s1, String s2) {
		return hierarchieTypeDown.get(s2).contains(s1);
	}
	

	public String toString() {
		String r = "";
		r = r + toStringHashMap(hierarchieTypeUp, "Hierarchy Montante");
		r = r + toStringHashMap(hierarchieTypeDown, "Hierarchy Descendante");
		// r = r +
		// toStringHashMap(componentstoFunctions,"Composant et Fonction");
		// r = r +
		// toStringHashMap(functionstoComponents,"Fonction et Composant");

		// r = r +
		// toStringHashMap(functiontoProvidedInterface,"Fonction et Interface");
		// r = r +
		// toStringHashMap(functiontoRequiredInterface,"Interface et Fonction");
		return r;
	}

	public String toStringHashMap(HashMap<String, ArrayList<String>> hash,
			String name) {
		String r = "============================================\n";
		r = r + "=== " + name + " \n";
		r = r + "============================================\n";
		for (String func : hash.keySet()) {
			r = r + func + " : \n";
			for (String strop : hash.get(func)) {
				r = r + "\t" + strop.toString() + "\n";
			}
			r = r + "\n";
		}
		return r;
	}

}
